/**
 * @file root form
 *
 * 跟 form 的区别在于此模型与服务器通信.
 *
 * @author liaoxuezhi@baidu.com
 */
/* eslint fecs-indent: [0, "space", 2, 2] */

import * as React from 'react';
import {
  PureComponent,
  PropTypes
} from 'react';
import {Renderer, render, buildApi, buildLink, noop, filter, normalizeLink} from '../renderer';
import * as actions from './actions';
import reducer from './reducer';
import './panel-form';
import {ValidationError} from '../../util/errors';
import * as deepEqual from 'deep-equal';
import * as assign from 'object-assign';
import {alert, confirm} from '../../widgets/alert/alert';
import * as pick from 'lodash/pick';
import {mapFormItem, mergeFormSchema, isAmisSite} from '../../util/helper';
import {setVariable, evalExpression} from '../util';
import {sendNotify} from '../../actions/asist';
import {push, goBack} from 'react-router-redux';
import OnExpression from '../hoc/on-expression';
const formProps = [
  'title', 'wrapWithPanel', 'controls', 'tabs', 'fieldSet', 'actions', 'submitText', 'mode', 'className',
  'horizontal', 'bodyClassName', 'collapsable', 'footer', 'footerClassName', 'panelClassName', 'autoFocus',
  'debug', 'attachChanged', 'changedKey', 'changedTrueValue', 'changedFalseValue', 'tabClassName', 'submitOnChange'
];
import * as Types from '../propTypes';
import * as schema from './form-schema.json';
import {buildSchemaApi} from '../util';


export class Form extends PureComponent {
  static propTypes = {
    name: PropTypes.string,
    api: Types.api,
    initApi: Types.api,
    asyncApi: Types.api, // 用来异步判断是否成功的 API
    schemaApi: Types.api,
    messages: PropTypes.shape({
      fetchSuccess: PropTypes.string,
      fetchFailed: PropTypes.string,
      saveFailed: PropTypes.string,
      saveSuccess: PropTypes.string
    }),
    wrapWithPanel: PropTypes.bool,
    data: PropTypes.object,
    locals: PropTypes.object,
    formRef: PropTypes.func,
    onSaved: PropTypes.func,
    onFailed: PropTypes.func,

    // actions
    // updateForm: PropTypes.func.isRequired,
    fetchForm: PropTypes.func.isRequired,
    fetchSchema: PropTypes.func.isRequired,
    saveForm: PropTypes.func.isRequired,
    doAction: PropTypes.func.isRequired,
    checkForm: PropTypes.func.isRequired,
    sendNotify: PropTypes.func.isRequired
  };

  static defaultProps = {
    state: {},
    messages: {},
    wrapWithPanel: true,
    data: {},
    formRef: noop,
    controlRef: noop,
    locals: {},
    checkInterval: 3000,
    primaryField: 'id',
    finishedField: 'finished',

    initFetch: true,
    initSchemaFetch: true,
    initAsyncApi: '',
    initFinishedField: 'finished',
    initCheckInterval: 3000,
    persistStore: false
  };

  static contextTypes = {
    scoped: PropTypes.object.isRequired
  };

  data = {};
  fetching = false;

  constructor(props) {
    super(props);

    this.state = {
      data: this.data = assign({}, props.state.data, props.data || {}),
      isReady: false,
      cb: null,
      action: null, // 当前 action 信息.
      schema: pick(props, formProps),
      isPristine: true,
      waiting: false,
      initWaiting: false
    };

    this.handleFormInit = this.handleFormInit.bind(this);
    this.handleFormChange = this.handleFormChange.bind(this);
    this.handleFormSubmit = this.handleFormSubmit.bind(this);
    this.handleFormAction = this.handleFormAction.bind(this);
    this.receive = this.receive.bind(this);
    this.reload = this.reload.bind(this);
    this.redirect = this.redirect.bind(this);
    this.checkFormFinished = this.checkFormFinished.bind(this);
    this.checkFormInitFinished = this.checkFormInitFinished.bind(this);
    this.fetchInitApi = this.fetchInitApi.bind(this);
  }

  componentWillMount() {
    const scoped = this.context.scoped;
    scoped.registerComponent(this);
  }

  componentWillReceiveProps(nextProps) {
    const props = this.props;
    const cb = this.state.cb;

    // todo 优化父级 dialog 传参.
    if (!deepEqual(props.data, nextProps.data)) {
      this.setState({
        data: this.data = assign({}, this.data, nextProps.data)
      });
    }

    if (
      formProps.some(key => props[key] !== nextProps[key])
    ) {
      this.setState({
        schema: pick(nextProps, formProps)
      });
    }

    // initApi 拉取的数据 merge 进来
    if (props.state.data !== nextProps.state.data) {
      this.setState({
        data: this.data = assign({}, this.data, nextProps.state.data),
        isReady: true
      });
    }

    // 提示获取数据情况
    if (props.state.fetching && !nextProps.state.fetching) {
      const {
        state: {
          invalid,
          error,
          message
        },
        messages,
        sendNotify,
      } = nextProps;

      if (invalid) {
        let msg = messages.fetchFailed || (error instanceof Error ? error.message : message) || '获取数据失败';
        sendNotify({
          message: msg,
          level: 'error'
        });
      } else if (messages.fetchSuccess) {
        sendNotify({
          message: messages.fetchSuccess,
          level: 'success'
        });
      }
      this.fetching = false;
    }

    // 提示保存数据情况.
    if (props.state.saving && !nextProps.state.saving) {
      const {
        state: {
          invalid,
          data,
          error,
          message
        },
        messages,
        sendNotify,
        onSaved,
        onFailed,
        finishedField
      } = nextProps;
      const action = this.state.action || {type: 'submit'};

      let msg = action && action.messages && action.messages.success || messages.saveSuccess || message || '保存成功';

      if (invalid) {
        msg = action && action.messages && action.messages.failed || messages.saveFailed || (error instanceof Error ? error.message : message) || '保存失败';
      }

      if (!invalid) {
        if (nextProps.asyncApi && !data[finishedField]) {
          return this.setState({
            waiting: true
          }, this.checkFormFinished);
        } else {
          sendNotify({
            message: msg,
            level: 'success'
          });

          onSaved && onSaved(data, action);

          const reload = action && action.reload || nextProps.reload;
          reload && this.context.scoped.reload(reload);

          const redirect = action && action.redirect || nextProps.redirect;
          redirect && this.redirect(redirect, assign({}, this.state.data, data));

          const feedback = action && action.feedback || nextProps.feedback;
          feedback && this.handleFeedback(feedback, assign({}, this.state.data, data));
        }
      } else {
        sendNotify({
          message: msg,
          level: 'error'
        });
        onFailed && onFailed();
      }

      cb && cb(invalid, data);
    }


    if (props.state.checking && !nextProps.state.checking) {
      const {
        state: {
          invalid,
          data,
          error,
          message
        },
        state,
        messages,
        sendNotify,
        onSaved,
        checkInterval,
        onFailed
      } = nextProps;

      const action = this.state.action || {type: 'button'};

      let msg = action && action.messages && action.messages.success || messages.saveSuccess || message || '保存成功';

      if (invalid) {
        msg = action && action.messages && action.messages.failed || messages.saveFailed || (error instanceof Error ? error.message : message) || '保存失败';
      }

      if (!invalid) {
        if (state.finished) {
          sendNotify({
            message: msg,
            level: invalid ? 'error' : 'success'
          });

          this.setState({
            waiting: false
          });

          if (action && action.type == 'submit') {
            onSaved && onSaved(data, action);

            const reload = action && action.reload || nextProps.reload;
            reload && this.context.scoped.reload(reload);

            const redirect = action && action.redirect || nextProps.redirect;
            redirect && this.redirect(redirect, assign({}, this.state.data, data));

            const feedback = action && action.feedback || nextProps.feedback;
            feedback && this.handleFeedback(feedback, assign({}, this.state.data, data));

            cb && cb(invalid, data);
          }
        } else if (checkInterval && this.state.waiting) {
          setTimeout(this.checkFormFinished, checkInterval);
        }
      }  else {
        this.setState({
          waiting: false
        });
        sendNotify({
          message: msg,
          level: invalid ? 'error' : 'success'
        });
        onFailed && onFailed();
        cb && cb(invalid, data);
      }
    }

    // initAsyncApi
    if (props.state.initChecking && !nextProps.state.initChecking) {
      const {
        state: {
          invalid,
          data,
          error,
          message
        },
        state,
        messages,
        sendNotify,
        onSaved,
        initCheckInterval,
        onFailed
      } = nextProps;

      let msg = messages.fetchSuccess || message || '拉取成功';

      if (invalid) {
        msg = messages.fetchFailed || (error instanceof Error ? error.message : message) || '拉取失败';
      }

      if (!invalid) {
        if (state.initFinished) {
          sendNotify({
            message: msg,
            level: invalid ? 'error' : 'success'
          });

          this.setState({
            initWaiting: false
          });
        } else if (initCheckInterval && this.state.initWaiting) {
          setTimeout(this.checkFormInitFinished, initCheckInterval);
        }
      }  else {
        this.setState({
          initWaiting: false
        });
        sendNotify({
          message: msg,
          level: invalid ? 'error' : 'success'
        });
      }
    }

    if (props.state.action.saving && !nextProps.state.action.saving) {
      const {
        state: {
          data,
          action: {
            invalid,
            error,
            message
          }
        },
        sendNotify,
        onSaved,
        finishedField,
        asyncApi
      } = nextProps;
      const action = this.state.action || {type: 'button'};

      let msg = action.messages && action.messages.success || message || '操作成功';

      if (invalid) {
        msg = action.messages && action.messages.failed || (error instanceof Error ? error.message : message) || '操作失败';

        sendNotify({
          message: msg,
          level: invalid ? 'error' : 'success'
        });
        cb && cb(true, data);
      } else {
        if (action.asyncApi || (action.type === 'submit' && asyncApi) && !data[finishedField]) {
          return this.setState({
            waiting: true
          }, this.checkFormFinished);
        } else {
          sendNotify({
            message: msg,
            level: 'success'
          });

          if (action.type === 'submit') {
            onSaved && onSaved(data, action);
          }

          cb && cb(false, data);
          action.reload && this.context.scoped.reload(action.reload);
          const redirect = action && action.redirect || action.type === 'submit' && nextProps.redirect;
          redirect && this.redirect(redirect, assign({}, this.state.data, data));
          action.feedback && this.handleFeedback(action.feedback, assign({}, this.state.data, data));
        }
      }
    }


    if (props.state.schema.fetching && !nextProps.state.schema.fetching) {
      const schema = nextProps.state.schema;

      if (schema.invalid) {
        let msg = (schema.error instanceof Error ? schema.error.message : schema.message) || '获取动态 Schema 失败';
        nextProps.sendNotify({
          message: msg,
          level: 'error'
        });
      } else {
        const newSchema = schema.data;
        const oldSchema = pick(nextProps, formProps); // this.state.schema;
        let mergedSchema = mergeFormSchema(oldSchema, newSchema);
        const data = assign({}, this.data);
        mapFormItem(mergedSchema, (item, paths) => {
          if (!item.name || !item.hasOwnProperty('value')) {
            return item;
          }

          const names = paths.map(item => item.name).filter(item => item);
          names.unshift(item.name);

          assign(data, setVariable(names.reverse().join('.'), item.value, data));
        });

        this.setState({
          schema: mergedSchema,
          data
        }, this.fetchInitApi);
      }
    }
  }

  componentDidUpdate(prevProps, prevState) {
    const {
      initApi,
      fetchForm,
      scopedKey,
      locals,
      state
    } = this.props;

    if (
      typeof initApi === 'string'
      && ~initApi.indexOf('$')
      && !this.fetching
      && this.state.data !== prevState.data
      && filter(initApi, prevState.data) !== filter(initApi, this.state.data)
    ) {
      this.fetching = true;
      fetchForm(scopedKey, initApi, this.state.data, locals);
    }
  }

  componentWillUnmount() {
    const scoped = this.context.scoped;
    scoped.unRegisterComponent(this);

    if (!this.props.persistStore) {
      this.props.clearForm(this.props.scopedKey);
    }
  }

  handleFormInit(model) {
    const {
      scopedKey,
      initApi,
      schemaApi,
      initSchemaFetch,
      fetchSchema,
      data,
      locals,
      initAsyncApi
    } = this.props;

    this.setState({
      data: this.data = assign({}, this.data, data, model),
      isReady: !initApi,
      isPristine: true,
      initWaiting: !initApi && initAsyncApi
    }, () => {
      if (schemaApi && initSchemaFetch) {
        return fetchSchema(scopedKey, buildSchemaApi(schemaApi), this.state.data, locals);
      }

      this.fetchInitApi();
    });
  }

  fetchInitApi() {
    const {
      scopedKey,
      initApi,
      fetchForm,
      locals,
      initFetch,
      initAsyncApi,
      fetchCheckForm,
      initFinishedField
    } = this.props;

    if (!initFetch) {
      return;
    }

    if (initApi) {
      fetchForm(scopedKey, initApi, this.state.data, locals);
    } else if(initAsyncApi) {
      fetchCheckForm(scopedKey, initAsyncApi, this.state.data, assign({}, this.state.data, {
        initFinishedField
      }));
    }
  }

  handleFormChange(model) {
    const {
      onFormChange
    } = this.props;

    model = assign({}, this.data, model);

    onFormChange && onFormChange(model);

    this.setState({
      data: this.data = model,
      isPristine: false
    });
  }

  handleFormSubmit(values) {
    const {
      saveForm,
      doAction,
      scopedKey,
      api,
      locals,
      target,
      redirect,
      onSubmit
    } = this.props;

    const action = this.state.action;
    const data = assign({}, this.state.data, values);

    if (target) {
      const scoped = this.context.scoped;
      scoped.receive(values, target);
      return;
    }

    if (action && (action.url || action.actionType === 'url')) {
      window.open(buildLink(action.url || action.action, data));
    } else if (action && (action.link || action.actionType === 'link')) {
      const push = this.props.push;
      const goBack = this.props.goBack;
      let fn = () => action.link === 'goBack' || action.action === 'goBack' ? goBack() : push(buildLink(action.link || action.action, data));
      return action.confirmText ? confirm(action.confirmText).then(fn) : fn();
    } else if (action && action.api) {
      action.confirmText ? confirm(action.confirmText).then(() => doAction(scopedKey, action.api, data, locals)) : doAction(scopedKey, action.api, data, locals);
    } else if (api) {
      let fn = () => saveForm(scopedKey, api, data, locals);
      action && action.confirmText ? confirm(action.confirmText).then(fn) : fn();
    } else if (this.state.cb) {
      this.state.cb();
    } else if (redirect) {
      this.redirect(redirect, data);
    } else if (onSubmit) {
      onSubmit(values);
    }
  }

  doAction(action, cb) {
    this.handleFormAction(action, null, cb)
  }

  handleFormAction(action, _, cb, ctxData = null) {
    let {
      scopedKey,
      doAction,
      locals,
      onRequestAction
    } = this.props;

    locals = assign({}, locals, this.state.data);
    const data = assign({}, ctxData ?  ctxData : this.form ? this.form.getModel() : this.state.data);
    const scoped = this.context.scoped;


    if (action.href || action.url || action.actionType === 'url' || action.link || action.actionType == 'link') {
      if (action.type === 'submit') {
        this.setState({
          action,
          cb
        }, () => this.form.submit());
      } else {
        const push = this.props.push;
        const goBack = this.props.goBack;
        let fn = () => action.link === 'goBack' || action.action === 'goBack' ? goBack() : push(buildLink(action.link || action.action, data));

        if (action.actionType === 'url' || action.url || action.href) {
          const mergedData = assign({}, this.props.data, data);
          fn = action.blank === false ? () => location.href = buildLink(action.url || action.href, mergedData) : () => window.open(buildLink(action.url || action.href, mergedData));
        }

        return action.confirmText ? confirm(action.confirmText).then(fn) : fn();
      }

    } else if (action.type === 'submit' || action.actionType === 'submit') {
      return this.setState({
        action,
        cb
      }, () => this.form.submit());
    } else if (action.type === 'reset' || action.actionType === 'reset') {
      this.form.reset();
    } else if (action.type === 'button' && action.actionType === 'ajax') {
      if (!action.api || action.action) {
        return alert('配有配置 api, 所以不能处理！');
      }

      let fn = () => this.setState({
        action,
        cb
      }, () => doAction(scopedKey, action.api || action.action, data, locals));

      return action.confirmText ? confirm(action.confirmText).then(fn) : fn();
    } else if (action.target && scoped) {
      let parts = action.target.split('.');
      const el = scoped.getComponentByName(parts.shift());
      el && el.reload && el.reload(parts.join('.'));
    } else if (onRequestAction) {
      // 不能支持的丢给父级取处理吧。
      onRequestAction(action, data);
    }
  }

  receive(values) {
    const {
      scopedKey,
      schemaApi,
      fetchSchema,
      fetchForm,
      initApi,
      locals,
      initAsyncApi,
      fetchCheckForm,
      initFinishedField,
      state
    } = this.props;

    this.setState({
      data: this.data = assign({}, this.data, values)
    }, () => {


      // 不推荐使用 schemaApi
      if (schemaApi) {
        fetchSchema(scopedKey, buildSchemaApi(schemaApi), this.state.data, locals);
      } else if (initApi) {
        if (!this.fetching) {
          this.fetching = true;
          fetchForm(scopedKey, initApi, this.state.data, locals);
        }
      } else if (initAsyncApi) {
        fetchCheckForm(scopedKey, initAsyncApi, this.state.data, assign({}, this.state.data, {
          initFinishedField
        }));
      } else {
        const inputs = this.form.inputs;
        inputs.forEach(input => input.reload && input.reload());
      }
    });
  }

  reload(subPath) {
    const form = this.form;

    if (subPath) {
      const inputs = form.inputs.filter(input => input.props.name === subPath);
      inputs.forEach(input => input.reload && input.reload());
    } else {
      const {
        scopedKey,
        fetchForm,
        schemaApi,
        fetchSchema,
        initApi,
        locals,
        initAsyncApi,
        fetchCheckForm,
        initFinishedField
      } = this.props;

      if (schemaApi) {
        fetchSchema(scopedKey, buildSchemaApi(schemaApi), this.state.data, locals);
      } else if (initApi) {
        fetchForm(scopedKey, initApi, this.state.data, locals);
      } else if (initAsyncApi) {
        fetchCheckForm(scopedKey, initAsyncApi, this.state.data, assign({}, this.state.data, {
          initFinishedField
        }));
      } else {
        form.inputs.forEach(input => input.reload && input.reload());
      }
    }
  }

  redirect(url, data) {
    url = filter(url, data);
    const flag = isAmisSite();

    if (/^goBack($|\?)/.test(url)) {
      flag || ~url.indexOf('spaMode') ? this.props.goBack() : history.goBack();
    } else {
      url = normalizeLink(url);
      (flag && !/^https?:/.test(url) || ~url.indexOf('spaMode')) ? this.props.push(url) : (location.href = url);
    }
  }

  handleFeedback(feedback, data) {
    const {onRequestAction} = this.props;

    if (onRequestAction) {
      onRequestAction({
        actionType: 'dialog',
        dialog: feedback
      }, data);
    } else {
      alert('不能处理 action 的 feedback');
    }
  }

  checkFormFinished() {
    const {
      checkForm,
      scopedKey,
      asyncApi,
      primaryField,
      finishedField,
      state
    } = this.props;

    const action = this.state.action;
    const data = this.state.data;
    checkForm(scopedKey, action && action.asyncApi || asyncApi, data[primaryField] ? {
      [primaryField]: data[primaryField]
    } : null, assign({}, this.state.data, action && action.api ? state.action.data : null, {
      finishedField
    }));
  }

  checkFormInitFinished() {
    const {
      fetchCheckForm,
      scopedKey,
      initAsyncApi,
      primaryField,
      initFinishedField,
      state
    } = this.props;

    const data = this.state.data;
    fetchCheckForm(scopedKey, initAsyncApi, data, assign({}, this.state.data, {
      initFinishedField
    }));
  }

  render() {
    let {
      $path,
      wrapWithPanel,
      state,
      formRef,
      locals,
      onInvalidSubmit,
      disabled,
      disabledOn,
      sendNotify,
      $key
    } = this.props;

    locals = assign({}, locals, {
      $formState: state
    });

    let loading = false;
    let loadingField = ''
    if(state.fetching) {
      loading = true;
      loadingField = 'fetching';
    } else if(state.saving) {
      loading = true;
      loadingField = 'saving';
    } else if(state.schema.fetching) {
      loading = true;
      loadingField = 'schema.fetching';
    } else if(state.action.saving || state.waiting) {
      loading = true;
      loadingField = 'waiting';
    }

    return render(`${$path}/${wrapWithPanel ? 'panel-form' : 'basic-form'}`, this.state.schema, {
      locals,
      formRef: form => formRef(this.form = form),
      errors: state.error && state.error.type === 'ValidationError' ? state.error.detail
        : state.action.error && state.action.error.type === 'ValidationError' ? state.action.error.detail : null,
      data: this.state.data,
      isReady: this.state.isReady,
      loading: loading/*state.fetching || state.saving || state.schema.fetching || state.action.saving || this.state.waiting*/,
      loadingField,
      disabled: disabledOn && evalExpression(disabledOn, this.state.data) || disabled,
      onInit: this.handleFormInit,
      onChange: this.handleFormChange,
      onSubmit: this.handleFormSubmit,
      onRequestAction: this.handleFormAction,
      onNotify: sendNotify,
      onInvalidSubmit
    }, $key);
  }
}

@Renderer({
  name: 'form',
  desc: '...',
  test: /(^|\/)form$/,
  reducer,
  schema
}, null, assign({
  push,
  goBack,
  sendNotify
}, actions))
@OnExpression
export default class FormRender extends Form {}
